package org.example.Controller;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController("/bit")
public class BitController {

    @Autowired
    @Qualifier("redisTemplate1")
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 应用场景
     *
     * 实际项目开发中有很多业务都适合采用redis的bit来实现。
     *
     * 用户签到场景
     *
     * 每天的日期字符串作为一个key，用户Id作为offset，统计每天用户的签到情况，总的用户签到数
     *
     * 活跃用户数统计
     *
     * 用户日活、月活、留存率等均可以用redis位数组来存储，还是以每天的日期作为key，用户活跃了就写入offset为用户id的位值1。
     *
     * 同理月活也是如此。
     *
     * 用户是否在线以及总在线人数统计
     *
     * 同样是使用一个位数组，用户的id映射偏移量，在线标识为1，下线标识为0。即可实现用户上下线查询和总在线人数的统计
     *
     * APP内用户的全局消息提示小红点
     *
     * 现在大多数的APP里都有站内信的功能，当有消息的时候，则提示一个小红点，代表用户有新的消息。
     */


    /**
     * 设置当前key的，位移offset，为ture,也就是1，false变true，false, true变true返回true,没有到有也是false
     * @param key
     * @return
     */
    @RequestMapping("/setBitTrue")
    public Boolean setBitTrue(String key) {
        //SETBIT key 1 1
        Boolean aBoolean = redisTemplate.opsForValue().setBit(key, 1, true);
        String o = redisTemplate.opsForValue().get(key).toString();
        //get取出来的没用，看不清楚，可以通过下面的getBit获取
        System.out.println("aBoolean="+aBoolean+"  ,o="+o);
        return aBoolean;
    }
    /**
     * 设置当前key的，位移offset，为false,也就是0，ture变false,返回true，false变false 返回false,没有到有也是false
     * @param key
     * @return
     */
    @RequestMapping("/setBitFalse")
    public Boolean setBitFalse(String key) {
        //SETBIT key 1 0
        return redisTemplate.opsForValue().setBit(key,1,false);
    }

    /**
     * 获得key对应的bit位移
     * @param key
     * @return
     */

    @RequestMapping("/getBit")
    public Boolean getBit(String key) {
        //GETBIT key offset
        return redisTemplate.opsForValue().getBit(key,1);
    }

    /**
     *BITFIELD能够在同一命令调用中使用多个位字段进行操作。它接受一个要执行的操作列表，并返回一个回复数组，其中每个数组与参数列表中的相应操作相匹配。
     * @param key
     * @return
     */
    @RequestMapping("/bitField")
    public List<Long> bitField(String key) {
        List<String> collect = Arrays.stream(key.split(",")).collect(Collectors.toList());
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create();
        return redisTemplate.opsForValue().bitField(key,bitFieldSubCommands.get(BitFieldSubCommands.BitFieldType.INT_8).valueAt(1));
    }

    /**
     * 用户签到-实现签到功能
     * https://blog.51cto.com/panyujie/5694240
     * @return
     */
    @RequestMapping("/sign")
    public Boolean sign() {

        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3.拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = "USER_SIGN_KEY"  + keySuffix;
        // 4.获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.写入Redis SETBIT key offset 1
        Boolean aBoolean = redisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
        return aBoolean;
    }


    /**
     *用户签到-签到统计
     *
     */
    @RequestMapping("/sign/count")
    public Boolean signCount() {
// 1.获取当前登录用户
        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3.拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = "USER_SIGN_KEY"  + keySuffix;
        // 4.获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.获取本月截止今天为止的所有的签到记录，返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
        List<Long> result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (result == null || result.isEmpty()) {
            // 没有任何签到结果
            return true;
        }
        Long num = result.get(0);
        if (num == null || num == 0) {
            return true;
        }
        // 6.循环遍历
        int count = 0;
        while (true) {
            // 6.1.让这个数字与1做与运算，得到数字的最后一个bit位  // 判断这个bit位是否为0
            if ((num & 1) == 0) {
                // 如果为0，说明未签到，结束
                break;
            }else {
                // 如果不为0，说明已签到，计数器+1
                count++;
            }
            // 把数字右移一位，抛弃最后一个bit位，继续下一个bit位
            num >>>= 1;
        }
        System.out.println(count);
        return false;
    }

}
